//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//------------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using Flare.Geom;

namespace Flare.Display
{
    /// <summary>
    /// The DisplayObjectContainer class is an abstract base class for objects that
    /// contain other objects on the display list. The list of children in the container
    /// are displayed in order with later objects appearing on top of previous objects.
    /// </summary>
    public abstract class DisplayObjectContainer : InteractiveObject
    {
        internal List<DisplayObject> m_children = new List<DisplayObject>();

        /// <summary>
        /// Indicates if children of the container are enabled for user input (e.g., mouse).
        /// </summary>
        public bool mouseChildren { get; set; }

        /// <summary>
        /// Gets the number children.
        /// </summary>
        public int numChildren
        {
            get { return m_children.Count; }
        }

        public DisplayObjectContainer()
        {
        }

        /// <summary>
        /// Adds the display object as a child of the container.
        /// 
        /// @throws ArgumentException If child is the container or one of its ancestors.
        /// </summary>
        public virtual void AddChild(DisplayObject child)
        {
            // Ensure the child can be added to the container
            if (this == child)
            {
                throw new global::System.ArgumentException(
                    "Can't add child DisplayObject to itself");
            }
            else
            {
                for (DisplayObjectContainer p = this; p != null; p = p.parent)
                {
                    if (p == child)
                    {
                        throw new global::System.ArgumentException(
                            "Can't add ancestor DisplayObject to a descent DisplayObject");
                    }
                }
            }

            if (child.parent != null)
            {
                child.parent.RemoveChild(child);
            }

            m_children.Add(child);
            child.parent = this;
        }

        /// <summary>
        /// Adds the display object as a child of the container at the specified index.
        /// 
        /// @throws IndexOutOfRangeException If the index is out of range.
        /// @throws ArgumentException If child is the container or one of its ancestors.
        /// </summary>
        public virtual void AddChildAt(DisplayObject child, int index)
        {
            if ((index < 0) || (index > m_children.Count))
            {
                throw new global::System.IndexOutOfRangeException();
            }

            // Ensure the child can be added to the container
            if (this == child)
            {
                throw new System.ArgumentException(
                    "Can't add child DisplayObject to itself");
            }
            else
            {
                for (DisplayObjectContainer p = this; p != null; p = p.parent)
                {
                    if (p == child)
                    {
                        throw new System.ArgumentException(
                            "Can't add ancestor DisplayObject to a descent DisplayObject");
                    }
                }
            }

            if (child.parent != null)
            {
                child.parent.RemoveChild(child);
            }

            m_children.Insert(index, child);
            child.parent = this;
        }
        
        /// <summary>
        /// Return a clone of the DisplayObjectContainer with a deep copy of any mutable members.
        /// </summary>
        public override object Clone()
        {
            DisplayObjectContainer clone = (DisplayObjectContainer)base.Clone();
            
            // Create a new empty children list since we're getting ready to populate it
            clone.m_children = new List<DisplayObject>();
            
            // Clone each of the children
            foreach (var child in this.m_children)
            {
                clone.AddChild((DisplayObject)child.Clone());
            }
            
            // Use the copied mouseChildren value
            
            return clone;
        }

        /// <summary>
        /// Returns true if the display object is contained in this container or any of
        /// its children.
        /// </summary>
        public virtual bool Contains(DisplayObject obj)
        {
            if (this == obj)
            {
                return true;
            }
            else
            {
                foreach (DisplayObject child in m_children)
                {
                    if (child == obj)
                    {
                        return true;
                    }
                    else if (child is DisplayObjectContainer)
                    {
                        if (((DisplayObjectContainer)child).Contains(obj))
                        {
                            return true;
                        }
                    }
                }

                return false;
            }
        }

        /// <summary>
        /// Returns the child display object at the specified index.
        /// 
        /// @throws IndexOutOfRangeException If the index is out of range.
        /// </summary>
        public virtual DisplayObject GetChildAt(int index)
        {
            if ((index < 0) || (index >= m_children.Count))
            {
                throw new global::System.IndexOutOfRangeException();
            }

            return m_children[index];
        }

        /// <summary>
        /// Returns the first child display object with the specified name.
        /// </summary>
        public virtual DisplayObject GetChildByName(string name)
        {
            foreach (DisplayObject child in m_children)
            {
                if (string.Equals(child.name, name))
                {
                    return child;
                }
            }

            return null;
        }

        /// <summary>
        /// Returns the index of the specified child display object.
        /// 
        /// @throws ArgumentException If the object isn't a child of the container.
        /// </summary>
        public virtual int GetChildIndex(DisplayObject obj)
        {
            int index = 0;

            foreach (DisplayObject child in m_children)
            {
                if (obj == child)
                {
                    return index;
                }
                ++index;
            }

            throw new global::System.ArgumentException(
                "DisplayObject isn't in DisplayObjectContainer");
        }
        
        internal override bool HitTest(float x, float y, bool shapeFlag, bool evaluateChildren)
        {
            // See if the point overlaps any children of the container
            if (evaluateChildren)
            {
                foreach (var child in this.m_children)
                {
                    if (child.HitTest(x, y, shapeFlag, evaluateChildren))
                    {
                        return true;
                    }
                }
            }
            
            return false;
        }

        /// <summary>
        /// Removes the child display object from the container.
        /// 
        /// @throws ArgumentException If the object isn't a child of the container.
        /// </summary>
        public virtual DisplayObject RemoveChild(DisplayObject child)
        {
            if (m_children.Remove(child))
            {
                child.parent = null;
            }
            else
            {
                throw new global::System.ArgumentException(
                    "DisplayObject isn't in DisplayObjectContainer");
            }

            return child;
        }

        /// <summary>
        /// Removes the child display object at the specified index from the container.
        /// 
        /// @throws IndexOutOfRangeException If the index is out of range.
        /// </summary>
        public virtual DisplayObject RemoveChildAt(int index)
        {
            if ((index < 0) || (index >= m_children.Count))
            {
                throw new global::System.IndexOutOfRangeException();
            }

            DisplayObject child = m_children[index];
            m_children.RemoveAt(index);
            child.parent = null;

            return child;
        }

        /// <summary>
        /// Removes all of the child display objects from the container.
        /// </summary>
        public virtual void RemoveChildren()
        {
            foreach (DisplayObject child in m_children)
            {
                child.parent = null;
            }

            m_children.Clear();
        }
        
        internal override void Render(RenderContext context)
        {
            if (!this.visible || this.isMask)
            {
                return;
            }

            for (int i = 0; i < this.m_children.Count; ++i)
            {
                this.m_children[i].Render(context);
            }
        }

        internal override void RenderAsMask(RenderContext context)
        {
            for (int i = 0; i < this.m_children.Count; ++i)
            {
                this.m_children[i].RenderAsMask(context);
            }
        }

        /// <summary>
        /// Sets the index of the child display object.
        /// 
        /// @throws IndexOutOfRangeException If the index is out of range.
        /// @throws ArgumentException If the object isn't a child of the container.
        /// </summary>
        public virtual void SetChildIndex(DisplayObject child, int index)
        {
            if ((index < 0) || (index >= m_children.Count))
            {
                throw new global::System.IndexOutOfRangeException();
            }

            if (!m_children.Remove(child))
            {
                throw new global::System.ArgumentException(
                    "DisplayObject isn't in DisplayObjectContainer");
            }
            m_children.Insert(index, child);
        }

        /// <summary>
        /// Swaps the position of the specified child display objects.
        /// 
        /// @throws ArgumentException If either child object isn't a child of the container.
        /// </summary>
        public virtual void SwapChildren(DisplayObject child1, DisplayObject child2)
        {
            if ((child1.parent != this) || (child2.parent != this))
            {
                throw new global::System.ArgumentException(
                    "DisplayObject isn't in DisplayObjectContainer");
            }

            SwapChildrenAt(GetChildIndex(child1), GetChildIndex(child2));
        }

        /// <summary>
        /// Swaps the position of the child display objects at the specified indices.
        /// 
        /// @throws IndexOutOfRangeException If either index is out of range.
        /// </summary>
        public virtual void SwapChildrenAt(int index1, int index2)
        {
            if ((index1 < 0) || (index1 >= m_children.Count) ||
                (index2 < 0) || (index2 >= m_children.Count))
            {
                throw new global::System.IndexOutOfRangeException();
            }

            DisplayObject obj = m_children[index1];
            m_children[index1] = m_children[index2];
            m_children[index2] = obj;
        }
    }
}